package falcon.script;

import java.util.HashMap;
import java.util.Stack;

import falcon.common.Errno;
import falcon.common.Log;
import falcon.script.FalconLangInstruction.Operand;
import falcon.script.FalconLangLoader.Procedure;

public final class FalconLangProcessor {
	
	public static final int FIX_TMP_COUNT = 16;
	public static final int DYN_TMP_COUNT = 16;
	public static final int TMP_COUNT = FIX_TMP_COUNT + DYN_TMP_COUNT;
	public static final int INVOKE_RET_COUNT = FIX_TMP_COUNT - 1;
	public static final int INVOKE_ARG_COUNT = INVOKE_RET_COUNT;
	
	public FalconLangProcessor(FalconLangGlobal global) {
		m_global = global;
		m_stack = new Stack<FalconLangProcessor.Context>();
		m_current = new Context(null, null, -1);
		m_tmps = new String[TMP_COUNT];
		m_target = null;
		m_retc = 0;
	}
	
	public int load(String path, String[] args) {
		FalconLangLoader loader = m_global.getLoader();
		Procedure procedure = loader.load(path);
		int errno = procedure.errno;
		if (errno != Errno.SUCCESS) {
			return errno;
		}
		m_current.args = args;
		m_current.procedure = procedure;
		m_current.pointer = 0;
		return Errno.SUCCESS;
	}
	
	public int execute() {		
		int errno;
		while (true) {
			errno = m_current.procedure.errno;
			if (errno != Errno.SUCCESS) {
				break;
			}
			boolean complete = m_current.pointer >= m_current.procedure.list.length;
			if (complete && !restore()) {
				break;
			}
			FalconLangInstruction instruction = m_current.procedure.list[m_current.pointer].instruction;
			m_current.pointer ++;
			int code = instruction.code;
			if (m_target != null && code != FalconLangInstruction.NOP) {
				continue;
			}
			if (code == FalconLangInstruction.NOP) {
				errno = executeNop(instruction);
			} else if (code == FalconLangInstruction.TEST) {
				errno = executeTest(instruction);
			} else if (code == FalconLangInstruction.MOV) {
				errno = executeMov(instruction);
			} else if (code == FalconLangInstruction.CALL) {
				errno = executeCall(instruction);
			} else if (code == FalconLangInstruction.RET) {
				errno = executeExit(instruction);
			} else if (code == FalconLangInstruction.JMP) {
				errno = Errno.SUCCESS;
			} else if (code == FalconLangInstruction.JE) {
				errno = executeEqual(instruction, code);
			} else if (code == FalconLangInstruction.JNE) {
				errno = executeEqual(instruction, code);
			} else if (code == FalconLangInstruction.JZ) {
				errno = Errno.SUCCESS;
			} else if (code == FalconLangInstruction.JNZ) {
				errno = Errno.SUCCESS;
			} else if (code == FalconLangInstruction.JG) {
				errno = executeComp(instruction, code);
			} else if (code == FalconLangInstruction.JGE) {
				errno = executeComp(instruction, code);
			} else if (code == FalconLangInstruction.JL) {
				errno = executeComp(instruction, code);
			} else if (code == FalconLangInstruction.JLE) {
				errno = executeComp(instruction, code);
			} else if (code == FalconLangInstruction.JOIN) {
				errno = executeJoin(instruction);
			} else if (code == FalconLangInstruction.ADD) {
				errno = executeCalc(instruction, code);
			} else if (code == FalconLangInstruction.SUB) {
				errno = executeCalc(instruction, code);
			} else if (code == FalconLangInstruction.MUL) {
				errno = executeCalc(instruction, code);
			} else if (code == FalconLangInstruction.DIV) {
				errno = executeCalc(instruction, code);
			} else if (code == FalconLangInstruction.MOD) {
				errno = executeCalc(instruction, code);
			} else {
				errno = Errno._OPERATION_NOT_PERMITTED;
			}
		}
		if (errno != Errno.SUCCESS) {
			trace();
		}
		return errno;
	}
	
	private void backup() {
		Context context = new Context(m_current);
		m_stack.push(context);
	}
	
	private boolean restore() {
		if (m_stack.isEmpty()) {
			return false;
		}
		for (int i = 1 + m_retc; i < FIX_TMP_COUNT; i ++) {
			m_tmps[i] = "";
		}
		Context context = m_stack.pop();
		m_current.args = context.args;
		m_current.procedure = context.procedure;
		m_current.pointer = context.pointer;
		m_target = null;
		m_retc = 0;
		return true;
	}
	
	private void trace() {
		FalconLangDebuger debuger = m_global.getDebuger();
		StringBuilder sb = new StringBuilder(1024);
		sb.append("list invoker stack\n");
		int count = m_stack.size();
		for (int i = count; i > -1; i --) {
			Context context = i == count ? m_current : m_stack.get(i);
			Procedure procedure = context.procedure;
			String path = procedure.path;
			int layer = count - i;
			sb.append(String.format("#### stack layer%03d;errno=%d ####\n", layer, procedure.errno));
			sb.append(String.format("# path=%s\n", path));
			if (procedure.errno != Errno.SUCCESS) {
				continue;
			}
			int offset = context.pointer;
			int row = procedure.list[offset].row;
			sb.append(String.format("# context: offset=%d;row=%d", offset, row + 1));
			sb.append("# assembly");
			debuger.printAssembly(procedure.path, sb);
		}
		Log.log(sb.toString());
	}
	
	private int executeNop(FalconLangInstruction instruction) {
		if (m_target == null) {
			return Errno.SUCCESS;
		}
		Operand op1 = instruction.operand1;
		if (m_target.equals(op1.value)) {
			m_target = null;
		}
		return Errno.SUCCESS;
	}
	
	private int executeTest(FalconLangInstruction instruction) {
		int errno = Errno.SUCCESS;
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		String v1 = getValue(op1);
		String v2 = getValue(op2);
		Long n1 = getNumberValue(v1);
		Long n2 = getNumberValue(v2);
		String res;
		if (n1 != null && n2 != null) {
			res = Long.toString(n1 - n2);
		} else {
			res = Boolean.toString(v1.equals(v2));
		}
		errno = setValue(op1, res);
		return errno;
	}
	
	private int executeMov(FalconLangInstruction instruction) {
		int errno = Errno.SUCCESS;
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		String value = getValue(op2);
		errno = setValue(op1, value);
		return errno;
	}

	private int executeCall(FalconLangInstruction instruction) {
		int errno = Errno.SUCCESS;
		Operand op1 = instruction.operand1;
		Long number = getNumberValue(instruction.operand2);
		if (op1 == null || number == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		String name = op1.value;
		int count = number.intValue();
		String[] args = new String[count];
		for (int i = 0; i < count; i ++) {
			args[i] = m_tmps[i];
		}
		if (op1.type == Operand.FUNC) {
			String path = m_global.getFunc(name);
			errno = FalconLangFuncCall.call(path, args, m_tmps);
		} else if (op1.type == Operand.FLOW) {
			backup();
			String path = m_global.getFlow(name);
			errno = load(path, args);
		} else if (op1.type == Operand.SYS) {
			errno = FalconLangSysCall.call(name, args, m_tmps);
		} else {
			errno = Errno._OPERATION_NOT_PERMITTED;
		}
		return errno;
	}
	
	private int executeExit(FalconLangInstruction instruction) {
		Operand op1 = instruction.operand1;
		if (op1 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		Long number = getNumberValue(op1);
		if (number == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		m_current.pointer = m_current.procedure.list.length;
		m_retc = number.intValue();
		return Errno.SUCCESS;
	}
	
	private int executeEqual(FalconLangInstruction instruction, int code) {
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		String value = getValue(op1);
		if (code == FalconLangInstruction.JE) {
			m_target = "true".equals(value) ? op2.value : null;
		} else if (code == FalconLangInstruction.JNE) {
			m_target = "false".equals(value) ? op2.value : null;
		} else {
			return Errno._OPERATION_NOT_PERMITTED;
		}
		return Errno.SUCCESS;
	}
	
	private int executeComp(FalconLangInstruction instruction, int code) {
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		Long number = getNumberValue(op1);
		if (number == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		if (code == FalconLangInstruction.JG) {
			m_target = number > 0L ? op2.value : null;
		} else if (code == FalconLangInstruction.JGE) {
			m_target = number >= 0L ? op2.value : null;
		} else if (code == FalconLangInstruction.JL) {
			m_target = number < 0L ? op2.value : null;
		} else if (code == FalconLangInstruction.JLE) {
			m_target = number <= 0L ? op2.value : null;
		} else {
			return Errno._OPERATION_NOT_PERMITTED;
		}
		return Errno.SUCCESS;
	}
	
	private int executeJoin(FalconLangInstruction instruction) {
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		String v1 = getValue(op1);
		String v2 = getValue(op2);
		return setValue(op1, v1 + v2);
	}
	
	private int executeCalc(FalconLangInstruction instruction, int code) {
		Operand op1 = instruction.operand1;
		Operand op2 = instruction.operand2;
		if (op1 == null || op2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		Long n1 = getNumberValue(op1);
		Long n2 = getNumberValue(op2);
		if (n1 == null || n2 == null) {
			return Errno.COM_CATCH_EXCEPTION;
		}
		if (code == FalconLangInstruction.ADD) {
			return setValue(op1, Long.toString(n1 + n2));
		} else if (code == FalconLangInstruction.SUB) {
			return setValue(op1, Long.toString(n1 - n2));
		} else if (code == FalconLangInstruction.MUL) {
			return setValue(op1, Long.toString(n1 * n2));
		} else if (code == FalconLangInstruction.DIV) {
			if (n2 == 0L) {
				Log.log("");
				return Errno.COM_CATCH_EXCEPTION;
			}
			return setValue(op1, Long.toString(n1 / n2));
		} else if (code == FalconLangInstruction.MOD) {
			if (n2 < 1L) {
				Log.log("");
				return Errno.COM_CATCH_EXCEPTION;
			}
			return setValue(op1, Long.toString(n1 % n2));
		}
		return Errno._OPERATION_NOT_PERMITTED;
	}
	
	private String getValue(Operand op) {
		if (op.type == Operand.CONST) {
			return op.value;
		} else if (op.type == Operand.TMP) {
			int index = Integer.parseInt(op.value);
			return m_tmps[index];
		} else if (op.type == Operand.VAR) {
			String v = m_current.vars.get(op.value);
			return v == null ? "" : v;
		} else if (op.type == Operand.ARG) {
			String v = op.value.startsWith("$") ? op.value.substring(1) : op.value;
			int index = Integer.parseInt(v);
			return m_current.args[index];
		}
		return "";
	}
	
	private int setValue(Operand op, String value) {
		if (op.type == Operand.TMP) {
			int index = Integer.parseInt(op.value);
			m_tmps[index] = value;
			return Errno.SUCCESS;
		} else if (op.type == Operand.VAR) {
			m_current.vars.put(op.value, value);
			return Errno.SUCCESS;
		}
		return Errno._OPERATION_NOT_PERMITTED;
	}
	
	private Long getNumberValue(Operand op) {
		String value = getValue(op);
		return getNumberValue(value);
	}
	
	private Long getNumberValue(String value) {
		if (!value.matches("^-?\\d+$")) {
			return null;
		}
		return Long.parseLong(value);
	}
	
	private static final class Context {
		
		public Context(String[] args, Procedure procedure, int pointer) {
			this.args = args;
			this.procedure = procedure;
			this.pointer = pointer;
			this.vars = new HashMap<String, String>();
		}
		
		public Context(Context context) {
			this(context.args, context.procedure, context.pointer);
		}
		
		public String[] args;
		public Procedure procedure;
		public int pointer;
		public HashMap<String, String> vars;
	}
	
	private FalconLangGlobal m_global;
	private Stack<Context> m_stack;
	private Context m_current;
	private String[] m_tmps;
	private String m_target;
	// FLOW 调用的返回值个数
	private int m_retc;
}
